package cn.fufeiii.bt.common.aspect.lock;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 全局锁切面
 *
 * @author FuFei
 */
@Order(20)
@Aspect
@Component
@ConditionalOnBean(RedissonClient.class)
public class GlobalLockAspect {
    /**
     * spring-el
     */
    private final LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
    private final SpelExpressionParser expressionParser = new SpelExpressionParser();

    /**
     * 切点方法缓存
     */
    private final Map<String, Method> methodCacheMap = new ConcurrentHashMap<>();

    @Autowired
    private RedissonLockHelper redissonLockHelper;
    @Autowired
    private GlobalLockProperty globalLockProperty;

    /**
     * 切点
     */
    @Pointcut("@annotation(GlobalLock)")
    public void pointcut() {
    }

    /**
     * Redisson全局锁切面
     */
    @Around("pointcut()")
    public Object advice(ProceedingJoinPoint joinPoint) throws Throwable {
        // 准备需要的变量
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        GlobalLock globalLock = method.getAnnotation(GlobalLock.class);

        // 加锁/解锁逻辑
        boolean isLockSuccess = false;
        RLock rLock = null;
        Object returnResult;
        List<String> finalKeyList = this.getFinalKeyList(globalLock.prefix(), globalLock.key(), method, joinPoint.getArgs());
        Logger log = LoggerFactory.getLogger(methodSignature.getDeclaringTypeName());
        boolean debugEnabled = log.isDebugEnabled();
        try {
            // 准备rLock
            rLock = this.getLock(finalKeyList);
            // 获取当前线程加锁是否成功
            isLockSuccess = rLock.tryLock(globalLock.waitTime(), TimeUnit.SECONDS);
            // 加锁失败抛出异常
            if (!isLockSuccess) {
                throw new IllegalStateException(String.format("加锁失败,线程%s: %s", Thread.currentThread().getName(), finalKeyList));
            }
            if (debugEnabled) {
                log.debug("加锁成功: {}", finalKeyList);
            }
            // 执行目标方法
            returnResult = joinPoint.proceed();
        } catch (Exception exception) {
            // 发生异常，判断异常处理策略
            GlobalLock.ExceptionHandleStrategy exceptionHandleStrategy = globalLock.exceptionHandleStrategy();

            // 静默处理
            if (GlobalLock.ExceptionHandleStrategy.SILENCE == exceptionHandleStrategy) {
                return null;
            }

            // 此时只剩下 FALLBACK策略 了，进行回退方法的判断，没有回退方法时抛出异常
            boolean needThrowException = StrUtil.isAllBlank(globalLock.fallback(), globalLock.defaultFallback());
            if (needThrowException) {
                throw exception;
            }
            if (debugEnabled) {
                log.debug("尝试执行回退方法[fbc-{},dfb-{},fb-{}]", globalLock.fallbackClass().getSimpleName(), globalLock.defaultFallback(), globalLock.fallback());
            }
            // 执行回退，若此期间发生异常，则不会在处理了
            returnResult = this.executeFallback(joinPoint, globalLock, exception);
        } finally {
            // 为了兼容RedissonMultiLock和RedissonLock，所以没有使用[isHeldByCurrentThread()]来作为解锁条件
            // 而根据tryLock是否成功来判断
            if (isLockSuccess) {
                // 加锁成功后解锁
                rLock.unlock();
                if (debugEnabled) {
                    log.debug("解锁成功: {}", finalKeyList);
                }
            }
        }
        return returnResult;
    }

    /**
     * 按需获取单个锁或者联锁
     *
     * @param finalKeyList 最终确定的锁名列表
     * @return *
     */
    private RLock getLock(List<String> finalKeyList) {
        if (finalKeyList.size() == 1) {
            return redissonLockHelper.getLock(finalKeyList.get(0));
        }
        return redissonLockHelper.getLock(finalKeyList);
    }

    /**
     * 获取锁名
     * 这个key可能是显示定义的，或者是springEL表达式，或者没有填写
     *
     * @param prefix   注解上的前缀
     * @param keyArray 注解上的锁名数组
     * @param method   被解析方法
     * @param args     被解析方法的参数
     * @return *
     */
    private List<String> getFinalKeyList(String prefix, String[] keyArray, Method method, Object[] args) {
        // 全局锁前缀
        String glPrefix = globalLockProperty.getNamespace() + StrUtil.COLON + prefix + (prefix.endsWith(StrUtil.COLON) ? "" : StrUtil.COLON);

        // 默认锁名
        boolean keyArrayIsEmpty = ArrayUtil.isEmpty(keyArray);
        if (keyArrayIsEmpty) {
            return Collections.singletonList(glPrefix + method.getDeclaringClass().getSimpleName() + ":" + method.getName());
        }

        // 准备设置sp-el的变量参数
        String spElFlag = "#";
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        // 参数列表为空
        if (ArrayUtil.isEmpty(parameterNames)) {
            return Arrays.stream(keyArray).map(it -> glPrefix + it).collect(Collectors.toList());
        }
        // 进行springEL解析
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }

        List<String> finalKeyList = new ArrayList<>();
        for (String rawKey : keyArray) {
            if (rawKey.contains(spElFlag)) {
                List<?> valueList = expressionParser.parseExpression(rawKey).getValue(context, List.class);
                // 空值则跳过
                if (CollUtil.isEmpty(valueList)) {
                    continue;
                }
                finalKeyList.addAll(valueList.stream().map(Object::toString).collect(Collectors.toList()));
            } else {
                finalKeyList.add(rawKey);
            }
        }

        // 若最终还没有key值，则抛出异常
        if (CollUtil.isEmpty(finalKeyList)) {
            throw new IllegalArgumentException();
        }
        return finalKeyList.stream().map(it -> glPrefix + it).collect(Collectors.toList());
    }

    /**
     * 当加锁失败时，执行回退逻辑
     *
     * @param joinPoint  连接点
     * @param globalLock 全局锁注解
     * @param exception  异常
     * @return *
     */
    private Object executeFallback(ProceedingJoinPoint joinPoint, GlobalLock globalLock, Exception exception) throws InvocationTargetException, IllegalAccessException {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Object target = joinPoint.getTarget();

        // 若指定了fallbackClass则使用其静态方法，反之则使用当前类的实例方法
        boolean isSpecifyFallbackClass = !Void.class.equals(globalLock.fallbackClass());
        // 若指定了defaultFallback则其函数入参只能有一个Exception参数，反之则是原函数参数加上Exception参数
        boolean isDefaultFallback = StrUtil.isNotBlank(globalLock.defaultFallback()) && StrUtil.isBlank(globalLock.fallback());

        // 从缓存中获取fallback函数，若不存在则放入缓存
        Method fallbackMethod;
        if (globalLockProperty.getUseFallbackMethodCache()) {
            String msKey = methodSignature.toString();
            // 注意不要用putIfAbsent，因为其会直接运算getFallbackMethod方法，而computeIfAbsent不会直接运算，因为其第二个参数不是Value值而是Function
            fallbackMethod = methodCacheMap.computeIfAbsent(msKey, k -> this.getFallbackMethod(target.getClass(), globalLock, methodSignature));
        } else {
            fallbackMethod = this.getFallbackMethod(target.getClass(), globalLock, methodSignature);
        }

        // 调用反射方法
        return fallbackMethod.invoke(isSpecifyFallbackClass ? null : target, isDefaultFallback ? new Object[]{exception} : ArrayUtil.append(joinPoint.getArgs(), exception));
    }

    /**
     * 根据注解的配置，获取回退函数
     *
     * @param targetClass     目标对象类型
     * @param globalLock      全局锁
     * @param methodSignature 连接点函数签名
     * @return *
     */
    private Method getFallbackMethod(Class<?> targetClass, GlobalLock globalLock, MethodSignature methodSignature) {
        String defaultFallbackMethodName = globalLock.defaultFallback();
        String fallbackMethodName = globalLock.fallback();
        Class<?> fallbackClass = globalLock.fallbackClass();
        // 若指定了fallbackClass则使用其静态方法，反之则使用当前类的实例方法
        boolean isSpecifyFallbackClass = !Void.class.equals(fallbackClass);
        // 若指定了defaultFallback则其函数入参只能有一个Exception参数，反之则是原函数参数加上Exception参数
        boolean isDefaultFallback = StrUtil.isNotBlank(defaultFallbackMethodName) && StrUtil.isBlank(fallbackMethodName);

        Class<?>[] parameterTypeArray = isDefaultFallback ? new Class<?>[]{Exception.class} : ArrayUtil.append(methodSignature.getParameterTypes(), Exception.class);
        Class<?> specifyClass = isSpecifyFallbackClass ? fallbackClass : targetClass;
        Method fallbackMethod = isDefaultFallback ?
                ClassUtils.getMethod(specifyClass, defaultFallbackMethodName, parameterTypeArray) : ClassUtils.getMethod(specifyClass, fallbackMethodName, parameterTypeArray);

        // 校验方法是否为静态函数
        if (isSpecifyFallbackClass && !Modifier.isStatic(fallbackMethod.getModifiers())) {
            throw new NoSuchMethodError(String.format("没有找到指定的静态回退方法[%s]", fallbackMethod));
        }
        // 校验出参是否一致(暂不考虑比较泛型)
        if (!methodSignature.getMethod().getReturnType().equals(fallbackMethod.getReturnType())) {
            throw new NoSuchMethodError(String.format("指定的静态回退方法[%s]返回参数类型不一致", fallbackMethod));
        }

        return fallbackMethod;
    }

}
